iT邦幫忙

2025 iThome 鐵人賽

DAY 25
0
Build on AWS

AWS架構師的自我修養:30天雲端系統思維實戰指南系列 第 33

Day 16-3 | Dev / Staging / Prod 多環境治理與架構策略: AWS 多環境配置管理與部署策略(end): 多環境的實際應用-藍綠部屬ECS/EC2與叢集(EKS)

  • 分享至 

  • xImage
  •  

5. CI/CD 管道整合

有鑑於我們在 <CI/CD 全自動化實作 - GitHub Actions × CodePipeline × CodeBuild> 中已經說過了關於 CI/CD 的整合與流程概念,我們接下來簡單的說一下流程並且整理出碎片化的 Jobs,然後進入到結合<Infrastructure as Code : Terraform 基礎設施代碼化與版本管控> 、<CI/CD 全自動化實作 - GitHub Actions × CodePipeline × CodeBuild> 、的多環境應用 藍綠部屬 環節

5.1 GitHub Actions 工作流程

重新複習一下,CI/CD 流程最重要的理念就是 建立一個穩定且固定的商業邏輯驗證交付流程, 積極性的保護既有商業邏輯不被程式碼異動所破壞汙染 `

每當有程式碼被推送(Push)到程式碼倉庫(例如 GitHub),流水線就自動啟動、它會抓取最新的程式碼並執行編譯、執行單元測試、程式碼品質掃描等一系列「品質檢查」。這就像是食品工廠的「品管環節」,每當有新的原料送達,品管員會立刻抽樣檢驗,確保這批原料沒有問題,不會污染整個生產線 - CI 的目標是 儘早發現問題 。當 CI 品管環節全部通過後,流水線會繼續下一步:打包(建立 Docker Image)、推送至倉庫(ECR)、然後自動觸發對 Dev -> Staging -> Prod 環境的部署,CD 的目標是讓發布成為一個 日常穩定可追蹤的事件,就像品管合格的原料,會被自動送上生產線,加工、烹飪、真空包裝,最後由輸送帶直接送到貨車上,配送至各大門市。

# .github/workflows/deploy.yml
name: Multi-Environment Deployment

on:
  push:
    branches: [release, develop]
  pull_request:
    branches: [release]

env:
  AWS_REGION: us-west-2
  ECR_REPOSITORY: demo-app

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      image-tag: ${{ steps.build.outputs.image-tag }}
    steps:
      - uses: actions/checkout@v3

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build and push Docker image
        id: build
        run: |
          IMAGE_TAG=${GITHUB_SHA:0:8}
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          echo "image-tag=$IMAGE_TAG" >> $GITHUB_OUTPUT

  deploy-dev:
    if: github.ref == 'refs/heads/develop'
    needs: build
    runs-on: ubuntu-latest
    environment: development
    steps:
      - name: Deploy to Development
        run: |
          # ECS deployment
          aws ecs update-service \
            --cluster dev-cluster \
            --service app-service \
            --task-definition app-service:LATEST

  deploy-staging:
    if: github.ref == 'refs/heads/main'
    needs: build
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: Deploy to Staging
        run: |
          # Kubernetes deployment
          kubectl set image deployment/app-deployment \
            app=$ECR_REGISTRY/$ECR_REPOSITORY:${{ needs.build.outputs.image-tag }} \
            -n staging

  deploy-prod:
    if: github.ref == 'refs/heads/main'
    needs: [build, deploy-staging]
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Deploy to Production
        run: |
          # Blue-Green deployment
          ./scripts/blue-green-deploy.sh ${{ needs.build.outputs.image-tag }}

而根據我們在 <CI/CD 全自動化實作 - GitHub Actions × CodePipeline × CodeBuild> 中所說的,<企業級 Jobs 模組化與跨領域引用> 當我們需要部署的時候,我們可以抽出共同流程(Jobs)將其流水線化抽出管理,並根據當前分支的行為進行自動化部屬

# build
parameters:
  - name: ngCliVersion
    type: string
    default: latest
    values:
      - latest
      - 16
      - 12
      - 11
jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      image-tag: ${{ steps.build.outputs.image-tag }}
    steps:
      - uses: actions/checkout@v3

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build and push Docker image
        id: build
        run: |
          IMAGE_TAG=${GITHUB_SHA:0:8}
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          echo "image-tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
#  deploy-dev
parameters:
  - name: ngCliVersion
    type: string
    default: latest
    values:
      - latest
      - 16
      - 12
      - 11
jobs:
  deploy-dev:
    if: github.ref == 'refs/heads/develop'
    needs: build
    runs-on: ubuntu-latest
    environment: development
    steps:
      - name: Deploy to Development
        run: |
          # ECS deployment
          aws ecs update-service \
            --cluster dev-cluster \
            --service app-service \
            --task-definition app-service:LATEST
#  deploy-staging
parameters:
  - name: ngCliVersion
    type: string
    default: latest
    values:
      - latest
      - 16
      - 12
      - 11

jobs:
  deploy-staging:
    if: github.ref == 'refs/heads/main'
    needs: build
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: Deploy to Staging
        run: |
          # Kubernetes deployment
          kubectl set image deployment/app-deployment \
            app=$ECR_REGISTRY/$ECR_REPOSITORY:${{ needs.build.outputs.image-tag }} \
            -n staging
#  deploy-prod
parameters:
  - name: ngCliVersion
    type: string
    default: latest
    values:
      - latest
      - 16
      - 12
      - 11

jobs:
  deploy-prod:
    if: github.ref == 'refs/heads/main'
    needs: [build, deploy-staging]
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Deploy to Production
        run: |
          # Blue-Green deployment
          ./scripts/blue-green-deploy.sh ${{ needs.build.outputs.image-tag }}

抽出完的流程會像是這樣

# .github/workflows/deploy.yml
name: Multi-Environment Deployment

on:
  push:
    branches: [release, develop]
  pull_request:
    branches: [release]

env:
  AWS_REGION: us-west-2
  ECR_REPOSITORY: demo-app

jobs:
  - template: jobs/build.yml@templates
  - template: jobs/deploy-dev.yml@templates
  - template: jobs/deploy-staging.yml@templates
    parameters:
      Version: ${{ variables.Version }}
      env: $(env)
      hasPreview: true

5.2 藍綠部署:單體實例(ECS/EC2)與叢集(EKS)

單體實例(ECS/EC2)的藍綠部屬

在上述我們簡單的複習了一下 CI/CD 流程與 Jobs 獨立切分之後,我們來了解多環境部屬的實際應用 - 藍綠部屬

現行系統開發中有兩大策略脈絡,其一是根據 降流直至服務徹底降溫 之後再部署的 滾動更新,而另一個則是 零停機時間 的發布策略 - 藍綠部署 (Blue/Green Deployment) 。滾動更新就像是在 F1 賽車時,只有進入到維修站並處於完全靜止( 降流直至服務徹底降溫 ) 的狀態下才能允許 快速進行維修與更新,在維修站中要與倒數的分秒進行賽跑,一旦出現失誤都將導致衝出維修站的時間被延宕,甚至如果開上賽道才發現第二個輪胎時發現新輪胎有問題,車子已經處於不穩定的狀態,要換回來必須要再次進入到維修站才能進行調整 - 而更糟的狀況是我們要在高速行駛中的 賽道上緊急進行搶修

藍綠部署 則提供了一種更安全、更優雅的哲學,我們不在行駛中的賽車( 藍色環境 )上動手。我們在旁邊的車道,準備一台一模一樣的、但安裝了新輪胎的新車( 綠色環境 )。我們讓這台新車發動、預熱、檢查所有儀表板,當我們 100% 確認新車完美無瑕時(這當然是最理想的狀況,哈哈),我們逐步將壓力切換到新車上。舊車則在旁邊逐步降載,萬一新車有問題,我們可以再 瞬間切換回來

  • 執行步驟:

    1. 初始狀態: 假設我們當前的生產環境是 V1,我們稱之為藍色環境。所有用戶流量都通過 Application Load Balancer (ALB) 指向藍色環境的 Target Group。

    2. 部署綠色環境:

    • CI/CD 流水線啟動。
    • 它會建立一套全新的、獨立的基礎設施(例如新的 ECS Service 或 K8s Deployment),部署上軟體的 V2 版本,這就是綠色環境。此時,綠色環境正在運行,但沒有任何真實的用戶流量。
    1. 驗證綠色環境:
    • 這是藍綠部署最關鍵的優勢。在切換流量之前,我們可以對綠色環境進行完整的、隔離的自動化測試。例如,從流水線內部發送模擬請求到綠色環境的內部端點,驗證其核心功能是否正常、API 是否回傳正確。
    1. 流量切換 (The Switch):
    • 當所有測試通過(通常還會加上一個「手動審批」的步驟),CI/CD 流水線會執行一個單一、原子性的操作:修改 ALB Listener 的規則,將 自定義 (視業務需求,但 20%會是一個常用的比例) 的流量從指向藍色 Target Group,改為指向綠色 Target Group,這個切換對用戶來說是瞬時的,他們絲毫感覺不到服務的中斷。
    1. 待命與下線:
    • 切換後,綠色環境逐步接手正式成為新的生產環境。
    • 舊的藍色環境並不會馬上被銷毀,它會作為一個「熱備份」在那裡待命。如果上線後幾分鐘或幾小時,監控系統發現 V2 版本有嚴重問題,我們可以執行「一鍵回滾」- 也就是再執行一次流量切換,把流量指回藍色環境,實現秒級災難恢復。
    • 當確認新版本穩定運行一段時間後,CI/CD 流水線才會去執行最後的清理工作,銷毀藍色環境以節省成本。

AWS CodeDeploy 這個服務與 ECS 和 EC2 深度整合,可以將上述複雜的藍綠部署流程自動化,我們只需要做一些簡單的配置即可。

我們剛剛討論的這套 「以 IaC 管理環境狀態、以 CI/CD 管理部署生命週期、逐步驗證環境差異」 的分工協作模式,正是當前雲端原生(Cloud-Native)領域中,討論度最高的、公認的 最佳化實現方案 (Optimal Implementation)黃金標準 (Gold Standard)

「最佳化」並非指它是「最簡單」或「最快速建立」的方案,而是指它在幾個關鍵的、往往相互衝突的工程目標之間,取得了最優雅的平衡。首先,它在 「穩定性」「敏捷性」 之間取得平衡,在過去想讓系統穩定,就意味著要減少變更,部署週期可能是數月一次。想要快速迭代(敏捷),又常常會犧牲穩定性,導致線上頻繁出錯。而現代化雲原生作法透過了 IaC 保障了「穩定性」的基石,我們的基礎設施不再是個黑盒子,每一次變更都有紀錄、有審核 (Code Review)、可追溯。這杜絕了因「手動誤操作」或「環境不一致」導致的大部分穩定性問題 - 基礎設施變得像岩石一樣穩固。CI/CD 提供了「敏捷性」的引擎,在穩固的基石上自動化流水線讓應用程式的部署變得極快且低風險。開發者可以每天進行數十次部署,而不用擔心會搞垮底層設施,藍綠部署等策略更是為這種高速迭代提供了頂級的安全網。

其次,需求團隊(Require)和維運團隊(Maintain)之間常常存在鴻溝。需求者想快速兌換新功能,維運者想穩定既有功能,互相指責、效率低下是常態。但 「以 IaC 管理環境狀態、以 CI/CD 管理部署生命週期、逐步驗證環境差異」「職責清晰」「團隊協作」 之間取得平衡,透過清晰的界線 (Clear Boundary)我們的分工模式提供了一個極其清晰的「契約」:

  • 平台/維運團隊 (Platform/Ops Team): 他們的核心產出是穩定、可靠、標準化的基礎設施平台(由 Terraform 定義)。他們像城市規劃者,負責建好水、電、瓦斯管線和標準化的建築地基。
  • 應用開發團隊 (Application/Dev Team): 他們的核心產出是高品質的應用程式(封裝在 Docker 裡),以及安全可靠的部署流程(由 GitHub Actions Workflow 定義)。他們像是在標準地基上蓋房子的建築隊,只需要專注於房子本身,而不用擔心水電是怎麼來的。

平台團隊提供穩定的平台後,應用團隊就可以透過 CI/CD 管道進行「自助式部署」,而不需要每次都去麻煩平台團隊,極大地提升了開發自主性和效率。

最後則是在 「可控性」「複雜性」 之間取得平衡,現代雲端系統的確非常複雜,動輒數百個資源和設定。雖然初學有門檻,但 IaC 是管理這種複雜度的唯一有效方法,它將龐雜的雲端資源,轉化為人類可讀、機器可執行的結構化文本。我們可以審視、測試、模組化我們的基礎設施,就像對待任何軟體一樣。同時 CI/CD 隱藏了複雜性,對於大多數開發者來說,他們不需要了解藍綠部署背後複雜的 ALB 規則切換細節。他們只需要知道「當我把程式碼合併到 main 分支,並在 Pipeline 點下『批准』按鈕後,我的新功能就會安全上線」- CI/CD 管道將複雜的部署過程,封裝成一個簡單、可靠的自動化流程。

叢集(EKS)的藍綠部屬

現在我們知道了藍綠部署的「哲學」與在 ECS 和 EC2 層級上的應用,現在就來看看 Kubernetes 這位「美食廣場總管」是如何用它手裡的工具,來具體執行這個精密的流量切換操作的。

與 ECS 不同,Kubernetes 本身沒有一個叫做 blue-green-deployment 的現成資源。但是,它提供了一系列更靈活、更強大的基礎元件(我們稱之為 primitives),讓我們可以像組合樂高一樣,搭建出比 AWS CodeDeploy 更靈活的藍綠部署流程,我們將重點介紹業界最主流、最能體現 Kubernetes 設計哲學的兩種實現方式:Service 層切換Ingress 層切換

複習一下,Kubernetes 裡的一個關鍵思想: Deployment/Pods (應用實例) 是實際在運行的「攤位」,它們是 會變動會生滅 的。而 Service/Ingress (服務入口) 是提供給內外部顧客的「固定攤位號碼」或「美食廣場入口」,它們應該是 穩定不變的 。K8s 藍綠部署的精髓,就在於保持「服務入口」不變,只改變它背後指向的「應用實例」。

Service Selector 切換 (最經典的 K8s 做法)

這是最能體現 Kubernetes 核心概念的方法,透過修改 Service 的 selector(標籤選擇器)來瞬間切換流量。依照我們之前在討論 EKS 時使用的案例 - 美食廣場,美食廣場的「B-52 叫號器」(Service) 本身不動,我們只是在它的後台系統裡,把服務人員從「藍色制服團隊」瞬間切換到「綠色制服團隊」。

示意 Steps:

  1. 初始狀態 (藍色環境運行中):
    • 我們有一個 Deployment-Blue ,它掌管著 v1 版本的 Pods。這些 Pods 身上都貼著標籤 version: blue
    # deployment-blue.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: myapp-blue
    spec:
    replicas: 3
    template:
      metadata:
        labels:
          app: myapp
          version: blue # 關鍵標籤
      spec:
        containers:
          - name: myapp
            image: my-app:v1
    
    • 我們有一個穩定的 Service,它的 selector 只會尋找身上貼有 version: blue 標籤的 Pods,並將流量轉發給它們。
    # service.yaml
    apiVersion: v1
    kind: Service
    metadata:
    name: myapp-service # 這是穩定不變的入口
    spec:
    selector:
      app: myapp
      version: blue # 目前指向藍色
    ports:
    - protocol: TCP
     port: 80
     targetPort: 8080
    
    此時,所有流量都流向 v1Pods
  2. 部署綠色環境:
    • CI/CD 管道會建立一個全新的 Deployment-Green,它掌管著 v2 版本的 Pods,這些 Pods 的標籤是 version: green
    # deployment-green.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: myapp-green
    spec:
    replicas: 3
    template:
      metadata:
        labels:
          app: myapp
          version: green # 關鍵標籤
      spec:
        containers:
          - name: myapp
            image: my-app:v2
    
    • 部署完成後,v2 的 Pods 已經在叢集中健康運行,但因為 myapp-service 的 selector 仍然是 version: blue,所以綠色環境完全沒有接收到任何正式流量。
  3. 驗證綠色環境
    • 在此時,我們可以建立一個僅供內部測試用的 Service,例如 myapp-green-test-service,它的 selector 指向 version: green。CI/CD 管道可以透過這個內部 Service 的位址,對綠色環境進行完整的自動化測試。
  4. 執行流量切換 (The Switch)
    • 當一切準備就緒,CI/CD 管道會執行一個單一、原子性的指令:
    kubectl patch service myapp-service -p '{"spec":{"selector":{"version":"green"}}}'
    
    • Kubernetes 的網路核心會瞬間更新路由規則。所有之後流向 myapp-service 這個穩定入口的流量,都會被立刻導向 version: greenPods。切換完成!
  5. 回滾與清理
    • 如果 v2 版本出現問題,回滾操作同樣簡單快速:
    kubectl patch service myapp-service -p '{"spec":{"selector":{"version":"blue"}}}'
    
    • 確認 v2 穩定運行後,CI/CD 管道就可以安全地刪除舊的藍色環境:
    kubectl delete deployment myapp-blue
    
Ingress 層切換 (更適合微服務與 Canary 發布)

這種方法更上一層樓,它不動 Service,而是修改 Ingress 的路由規則。 這次,「藍色團隊」和「綠色團隊」有各自獨立的內部叫號器 ( service-blueservice-green )。我們去修改的是美食廣場總入口的導覽看板 (Ingress),將上面「A+ 炸雞排」的指向,從「藍色櫃檯」改成「綠色櫃檯」。

示意 Steps:

  1. 初始狀態:
    • deployment-bluedeployment-green
    • 有兩個 Service:service-blue 指向 version: bluePods,service-green 指向 version: greenPods
    • 有一個 Ingress 資源,其規則指向 service-blue
    # ingress.yaml
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: myapp-ingress
    spec:
      rules:
        - host: myapp.example.com
          http:
            paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: service-blue # 指向藍色服務
                    port:
                      number: 80
    
  2. 流量切換:
    • CI/CD 管道執行指令來修改 Ingress 資源,將 backend.service.nameservice-blue 改為 service-green
    # 簡易範例,實務上會用 kubectl patch 或 apply -f
    # 修改 ingress.yaml 中的 service name 後執行 apply
    kubectl apply -f ingress.yaml
    
    • Ingress Controller (如 AWS Load Balancer Controller) 偵測到變更後,會去更新 ALB 的轉發規則,將流量導向 service-green。

這種模式是實現金絲雀發布 (Canary Release) 的基礎,我們可以配置 Ingress,將 95% 的流量發送到 service-blue,5% 的流量發送到 service-green,實現小規模灰度驗證。也因為 Service 層的變動較少,更符合其「穩定入口」的定位,職責更清晰。

我們現在掌握的這套結合 IaC、容器化、CI/CD 和高級部署策略 的思維模型,不僅僅是一套技術操作指南,它是一種 現代軟體工程的哲學 。它是 Google、Netflix、Amazon 等頂級科技公司能夠每週進行數千次部署,同時依然保持系統高度穩定性的核心秘訣。因為它並非單純地解決一個技術問題,而是在解決一個系統性工程問題,它深刻理解到軟體交付不僅僅是寫程式碼,還包括了測試、部署、維運、團隊協作、風險管控等一系列環節。

在未來的職業生涯中,無論工具如何演進(從 Terraform 到 OpenTofu,從 GitHub Actions 到 GitLab CI),這背後關於 狀態與流程分離聲明式與命令式協作 的核心思想,將會長期適用,並成為構建高品質軟體系統的堅實基礎。工具會隨著時代演進,但設計哲學是邏輯的推導辯證過程,就像

《荀子·儒效》:“千舉萬變,其道一也。”

又或是我較喜歡的莊子說的

《莊子·天下》:“不離於宗,謂之天人。”

6. 配置管理與秘密管理

今天(?)的最後,我們來說說關於 環境參數授權公鑰連線端口該怎麼管理吧

到目前為止,我們的自動化美食廣場已經非常先進了:有標準化的攤位藍圖 (IaC)、標準化的料理包 (Docker)、高效的總管 (ECS/EKS),還有一條全自動的配送流水線 (CI/CD)。

但現在,我們要處理一個極度敏感且重要的問題: 每個攤位的「獨家秘方」(Secrets) 要放在哪裡?

我們總不能把「A+ 炸雞排」的獨門醃料配方,用一張紙條貼在 DockerfileGit 倉庫的牆上吧?這絕對是安全上的災難。所以接下來我們要學習蟹老闆的精神,死死捂好我們的機密資料,將「配置 (Configuration)」與「秘密 (Secrets)」從我們的應用程式碼中分離出來,並在需要時才「注入」給對的應用。

K8s 的原生解決方案 — ConfigMap & Secret

Kubernetes 提供了兩種原生的資源,來處理這個問題。

  1. ConfigMap - 「公開的菜單與公告」

    • 用途: 用於儲存非敏感性的配置資料。
    • 比喻: 想像一下美食廣場的公告欄。上面貼著各種公開資訊:
      • API_URL: https://api.internal.mycompany.com (後端 API 的位址)
      • FEATURE_FLAG_NEW_UI: "true" (是否啟用新介面的功能開關)
      • LOG_LEVEL: "info" (日誌紀錄的詳細等級)
    • 特性:
      • 以鍵值對 (key-value pairs) 的形式儲存純文字資料。
      • 非常靈活,可以透過兩種主要方式被 Pod 使用:
        1. 注入為環境變數 (Environment Variables): 這是最常見的方式。
        2. 掛載為檔案 (Mounted as a file): 當配置內容較大或格式複雜(例如一個 nginx.conf 檔案)時,可以將整個 ConfigMap 掛載到 Pod 的檔案系統中。
    • 重點: ConfigMap 是純文字,絕不能用來存放任何敏感資訊。
  2. Secret - 「鎖在普通抽屜裡的秘方」

    • 用途: 用於儲存敏感性資料。
    • 比喻: 這就像是攤位店長放在自己辦公室一個普通抽屜裡的「秘方筆記本」。上面可能寫著:
      • DATABASE_PASSWORD: "mypassword123"
      • STRIPEAPI_KEY: "sk_live..."
    • 特性:
      • 同樣以鍵值對的形式儲存。
      • 與 ConfigMap 最大的不同是,Kubernetes 儲存 Secret 的值時,會對其進行 Base64 編碼。
        • Base64 不是加密! 它只是一種編碼方式,任何人只要拿到編碼後的字串,都可以輕鬆地解碼回原文。
        1. 注入為環境變數 (Environment Variables): 這是最常見的方式。
        2. 掛載為檔案 (Mounted as a file): 當配置內容較大或格式複雜(例如一個 nginx.conf 檔案)時,可以將整個 ConfigMap 掛載到 Pod 的檔案系統中。
    • 重點: Kubernetes Secret 的安全性主要依賴於 RBAC (Role-Based Access Control)。我們可以設定嚴格的權限,規定只有特定的 Pod 或管理員才有權限「讀取」這個 Secret 物件。它防君子不防小人,主要防止的是資訊的意外洩漏。
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: ${ENVIRONMENT}
data:
  NODE_ENV: ${ENVIRONMENT}
  LOG_LEVEL: ${LOG_LEVEL}
  API_BASE_URL: ${API_BASE_URL}
---
apiVersion: v1
kind: Secret
metadata:
  name: database-secret
  namespace: ${ENVIRONMENT}
type: Opaque
data:
  url: ${DATABASE_URL_BASE64}

但對於一個嚴肅的生產環境,單純使用 K8s Secret 是不夠的。就像我們剛剛說的 防君子不防小人,主要防止的是資訊的意外洩漏,這就像把秘方鎖在一個誰都可以輕易撬開的抽屜裡。我們還缺少了:

  • 靜態加密 (Encryption at Rest): 秘方在資料庫(etcd)中只是編碼,不是加密。
  • 存取稽核 (Audit Trail): 誰在什麼時候偷看了秘方?(有人偷看了罷?)我們無從得知。
  • 自動輪換 (Automatic Rotation): 為了安全,密碼應該定期更換。手動更換既繁瑣又容易出錯。

AWS 的頂級保險櫃 — Secrets Manager

為了解決上述問題,雲端服務商提供了專門的「秘密管理服務」。在 AWS,這個服務就是 AWS Secrets Manager。

  • 用途: 集中式地儲存、管理、稽核、輪換你所有的敏感秘密。
  • 比喻: 這不再是一個普通的抽屜,而是一個銀行級的頂級保險櫃。
  • 核心優勢:
    1. 強加密: 所有秘密在儲存時,都使用 AWS KMS (Key Management Service) 進行了強大的加密。
    2. 精細的 IAM 權限控制: 你可以透過 IAM Policy,精確到「哪個應用(哪個 IAM Role)只能讀取哪個秘密的哪個版本」。
    3. 完整的稽核紀錄: 與 CloudTrail 深度整合。每一次對秘密的讀取、修改、刪除操作,都會被詳細地記錄下來,供安全團隊審計。
    4. 自動輪換: 這是 Secrets Manager 的王牌功能。它可以與 RDS 等服務整合,實現資料庫密碼的全自動定期輪換,而你的應用程式完全不需要修改程式碼或重啟。
# terraform/secrets.tf
resource "aws_secretsmanager_secret" "database_url" {
name = "${var.environment}/database-url"
description = "Database connection string for ${var.environment}"

tags = {
Environment = var.environment
Project     = "multi-env-demo"
}
}

resource "aws_secretsmanager_secret_version" "database_url" {
secret_id     = aws_secretsmanager_secret.database_url.id
secret_string = jsonencode({
url = var.database_url
})
}

最佳實踐:讓 K8s Pod 直接從 AWS 保險櫃取貨

現在,我們有了 K8s 的應用 (Pod),也有了 AWS 的保險櫃 (Secrets Manager)。問題是,如何讓 Pod 安全地拿到保險櫃裡的秘方,而不是透過不可靠的人工傳遞?

這就要用到一個關鍵的「橋樑」技術:IAM Roles for Service Accounts (IRSA),以及一個重要的工具:AWS Secrets and Configuration Provider (ASCP)。

就像我們不會把保險櫃的密碼告訴攤位廚師(Pod)。相反的,我們給這位廚師頒發一張特殊的 「授權員工卡」(Service Account with an IAM Role) 。當廚師需要秘方時,他拿著這張卡去刷銀行的特定閘門(ASCP),銀行系統驗證卡片權限後,會自動把今天需要用的那一頁秘方,臨時放到他面前一個專用的、有鎖的盒子里(掛載為 Pod 內的檔案)。廚師用完後盒子就銷毀。

運作流程:

  1. 設定 IRSA:

    • 在 AWS IAM 中,建立一個 Role,授權它可以讀取 my-app/db-password 這個 Secret。
    • 在 EKS 中,建立一個 Kubernetes ServiceAccount。
    • 將這兩者「綁定」,告訴 EKS:「凡是使用這個 ServiceAccount 的 Pod,都可以扮演那個 IAM Role 的身份。」
  2. 在 Deployment 中指定 ServiceAccount:

    • 在你的 deployment.yaml 中,明確指定 spec.template.spec.serviceAccountName 為你剛剛建立的 ServiceAccount。
  3. 使用 ASCP 掛載秘密:

    1. 你需要先在 EKS 叢集中安裝 AWS Secrets and Configuration Provider 這個附加元件。

    2. 然後,在你的 deployment.yaml 中,定義一個 Volume,告訴 ASCP:

       1. 我要掛載一個秘密。
       2. 秘密的名稱是 my-app/db-password (在 Secrets Manager 中的名稱)。
       3. 請把它掛載到 Pod 裡的 /etc/secrets/db-password 這個路徑。
      
  4. 應用程式讀取秘密: - 你的應用程式啟動後,它不再從環境變數讀取密碼,而是直接去讀取本地檔案 /etc/secrets/db-password 的內容。

這個方案的巨大優勢:

  • 最高安全性: 秘密從未以明文形式出現在 Kubernetes 的任何資源(YAML、環境變數)中。它從 AWS Secrets Manager 加密傳輸,直接掛載到 Pod 的記憶體檔案系統中。
  • 零程式碼耦合: 你的應用程式不知道也不關心秘密是來自 AWS 還是其他地方,它只需要從一個固定的檔案路徑讀取即可。
  • 動態更新: 當你在 Secrets Manager 中更新了秘密的值,ASCP 可以近乎即時地在 Pod 內更新掛載的檔案內容,應用程式可以動態讀取到最新的值。

最後我們建立了一個層次分明的配置與秘密管理策略:

ConfigMap: 用於存放那些公開無妨的應用程式配置。

K8s Secret: 作為一個基礎的秘密管理工具,主要靠 RBAC 保護,適用於一些敏感度不高的場景。

AWS Secrets Manager + IRSA + ASCP 是在 EKS 上管理高敏感性秘密的黃金標準和最佳實踐。它提供了端到端的加密、稽核與自動輪換能力,是構建安全可靠系統的必要一環。

接下來讓我們暫且賣個關子,將 監控與日誌管理安全性最佳實踐災難恢復與備份 這三個主題放在未來 發佈與運營監控(安全與合規) 這個階段的時候詳細談談,我們今天(?) 最主要討論的是 軟體開發與持續整合 - Dev / Staging / Prod 多環境治理與架構策略。 而明天我們將討論的是 軟體開發與持續整合 最後一個內容 - 開發者體驗(DX)優化:內部工具與排錯設計。


上一篇
Day 16-2 | Dev / Staging / Prod 多環境治理與架構策略: AWS 多環境配置管理與部署策略(二): ECS 、 EKS 與 K8s 管理
下一篇
Day 17 | 開發者體驗(DX)優化架構:商業價值、內部工具與排錯設計 - DX 的商業價值:為什麼老闆和財務長也會心動 DX
系列文
AWS架構師的自我修養:30天雲端系統思維實戰指南35
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言